import {
	authentication,
	AuthenticationProvider,
	AuthenticationProviderAuthenticationSessionsChangeEvent,
	AuthenticationSession,
	Disposable,
	Event,
	EventEmitter,
	SecretStorage,
	window,
} from 'vscode';

class AzureDevOpsPatSession implements AuthenticationSession {
	//我们不知道用户的帐户名，所以我们只使用常数
	readonly account = { id: AzureDevOpsAuthenticationProvider.id, label: 'Personal Access Token' };
	// 这个id在本例中没有任何用途，所以我们将其设置为一个常量，在实际应用中，这个 id 应该是唯一的，用于区分不同的认证会话。  
	readonly id = AzureDevOpsAuthenticationProvider.id;
	// 我们不知道PAT有什么作用域，所以这里我们有一个空数组。在实际应用中，这个数组应该包含具体的权限范围
	readonly scopes = [];

	/**
	 * 
	 * @param accessToken 用于身份验证的个人访问令牌
	 */
	constructor(public readonly accessToken: string) { }
}

export class AzureDevOpsAuthenticationProvider implements AuthenticationProvider, Disposable {
	static id = 'azuredevopspat';
	private static secretKey = 'AzureDevOpsPAT';

	// 这个属性用来确定令牌是否在VS Code的另一个窗口中被更改。
	// 在 checkForUpdates 函数中使用
	private currentToken: Promise<string | undefined> | undefined;
	private initializedDisposable: Disposable | undefined;

	private _onDidChangeSessions = new EventEmitter<AuthenticationProviderAuthenticationSessionsChangeEvent>();
	get onDidChangeSessions(): Event<AuthenticationProviderAuthenticationSessionsChangeEvent> {
		return this._onDidChangeSessions.event;
	}

	constructor(private readonly secretStorage: SecretStorage) { }

	dispose(): void {
		this.initializedDisposable?.dispose();
	}

	private ensureInitialized(): void {
		if (this.initializedDisposable === undefined) {
			void this.cacheTokenFromStorage();

			this.initializedDisposable = Disposable.from(
				// 这个onDidChange事件发生在 secret storage 在任何窗口发生变化的时候
				// 所有打开的窗户都共享secrets。
				this.secretStorage.onDidChange(e => {
					if (e.key === AzureDevOpsAuthenticationProvider.secretKey) {
						void this.checkForUpdates();
					}
				}),
				// 当用户通过Accounts菜单启动“静默”认证流程时，会触发此命令
				authentication.onDidChangeSessions(e => {
					if (e.provider.id === AzureDevOpsAuthenticationProvider.id) {
						void this.checkForUpdates();
					}
				}),
			);
		}
	}

	// 这是一个至关重要的函数，它处理令牌是否在VS Code的不同窗口中发生了变化，并在发生变化时发送必要的事件。
	private async checkForUpdates(): Promise<void> {
		const added: AuthenticationSession[] = [];
		const removed: AuthenticationSession[] = [];
		const changed: AuthenticationSession[] = [];

		const previousToken = await this.currentToken;
		const session = (await this.getSessions())[0];

		if (session?.accessToken && !previousToken) {
			added.push(session);
		} else if (!session?.accessToken && previousToken) {
			removed.push(session);
		} else if (session?.accessToken !== previousToken) {
			changed.push(session);
		} else {
			return;
		}

		void this.cacheTokenFromStorage();
		this._onDidChangeSessions.fire({ added: added, removed: removed, changed: changed });
	}

	private cacheTokenFromStorage() {
		this.currentToken = this.secretStorage.get(AzureDevOpsAuthenticationProvider.secretKey) as Promise<string | undefined>;
		return this.currentToken;
	}

	// 当调用 vscode.authentication.getSessions 时，这个函数会首先被调用。
	async getSessions(_scopes?: string[]): Promise<readonly AuthenticationSession[]> {
		this.ensureInitialized();
		const token = await this.cacheTokenFromStorage();
		return token ? [new AzureDevOpsPatSession(token)] : [];
	}

	// 当调用 this.getSessions 之后，并且仅当以下情况时，会调用此函数：
	// - this.getSessions 没有返回任何内容，但在 vscode.authentication.getSessions 中将 createIfNone 设置为 true
	// - `调用 vscode.authentication.getSessions 时将 forceNewSession 设置为 true
	// -  最终用户通过“帐户”菜单启动了“无提示”认证流程
	async createSession(_scopes: string[]): Promise<AuthenticationSession> {
		this.ensureInitialized();

		// 提示输入PAT（个人访问令牌）。
		const token = await window.showInputBox({
			ignoreFocusOut: true,
			placeHolder: 'Personal access token',
			prompt: 'Enter an Azure DevOps Personal Access Token (PAT).',
			password: true,
		});

		// 注意：除了确保令牌不为空之外，此示例没有对令牌进行任何其他验证。
		if (!token) {
			throw new Error('PAT is required');
		}

		// 不要在这里设置currentToken，因为我们希望在checkForUpdates调用中触发适当的事件
		await this.secretStorage.store(AzureDevOpsAuthenticationProvider.secretKey, token);
		console.log('Successfully logged in to Azure DevOps');

		return new AzureDevOpsPatSession(token);
	}

	// 当最终用户登出账户时，会调用此函数。
	async removeSession(_sessionId: string): Promise<void> {
		const token = await this.currentToken;
		if (!token) {
			return;
		}
		await this.secretStorage.delete(AzureDevOpsAuthenticationProvider.secretKey);
		this._onDidChangeSessions.fire({ removed: [new AzureDevOpsPatSession(token)] });
	}
}
